package com.sgcc.nfc.lock.presentation.map

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable
import android.graphics.Bitmap
import android.location.Location
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.app.ActivityCompat
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.amap.api.location.AMapLocation
import com.amap.api.location.AMapLocationClient
import com.amap.api.location.AMapLocationClientOption
import com.amap.api.maps.AMap
import com.amap.api.maps.CameraUpdateFactory
import com.amap.api.maps.model.BitmapDescriptor
import com.amap.api.maps.model.BitmapDescriptorFactory
import com.amap.api.maps.model.LatLng
import com.amap.api.maps.model.Marker
import com.amap.api.maps.model.MarkerOptions
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.sgcc.nfc.lock.R
import com.sgcc.nfc.lock.databinding.DialogMapFilterBinding
import com.sgcc.nfc.lock.databinding.FragmentMapBinding
import com.sgcc.nfc.lock.databinding.LayoutMapInfoWindowBinding
import com.sgcc.nfc.lock.domain.model.MapFilter
import com.sgcc.nfc.lock.domain.model.MapPoint
import com.sgcc.nfc.lock.domain.model.MapStats
import com.sgcc.nfc.lock.domain.model.Region
import com.sgcc.nfc.lock.domain.model.RoomStatus
import com.sgcc.nfc.lock.domain.model.isClosed
import com.sgcc.nfc.lock.domain.model.isIdle
import com.sgcc.nfc.lock.domain.model.isProblematic
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MapFragment : Fragment() {

    private var _binding: FragmentMapBinding? = null
    private val binding get() = _binding!!
    private val viewModel: MapViewModel by viewModels()
    private var aMap: AMap? = null
    private val markers = mutableListOf<Marker>()
    private var lastPoints: List<MapPoint> = emptyList()
    private var lastKnownLocation: LatLng? = null
    private val markerDescriptorCache = mutableMapOf<Int, BitmapDescriptor>()
    private var locationClient: AMapLocationClient? = null
    private var isLocating = false
    private var pendingLocationRequest = false
    private var lastErrorMessage: String? = null
    
    // 定时刷新相关
    private val refreshHandler = Handler(Looper.getMainLooper())
    private var refreshRunnable: Runnable? = null
    private val refreshIntervalMs = 30000L // 30秒刷新一次

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentMapBinding.inflate(inflater, container, false)
        binding.mapView.onCreate(savedInstanceState)
        aMap = binding.mapView.map
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupMap()
        observeViewModel()
        binding.fabFilter.setOnClickListener { showFilterDialog() }
        binding.fabRefresh.setOnClickListener { viewModel.loadPoints() }
        binding.emptyStateRetryButton.setOnClickListener { viewModel.loadPoints() }
        binding.errorStateRetryButton.setOnClickListener { viewModel.loadPoints() }
        binding.fabLocation.setOnClickListener { onLocateButtonClicked() }
        startLocation()
    }

    private fun setupMap() {
        val map = aMap ?: return
        map.uiSettings.isZoomControlsEnabled = true
        map.uiSettings.isMyLocationButtonEnabled = false
        map.setOnMarkerClickListener { marker ->
            marker.showInfoWindow()
            true
        }
        map.setOnInfoWindowClickListener { marker ->
            (marker.`object` as? MapPoint)?.let { navigateToRoomDetail(it.roomId) }
        }
        map.setInfoWindowAdapter(object : AMap.InfoWindowAdapter {
            override fun getInfoWindow(p0: Marker): View = createInfoWindow(p0)
            override fun getInfoContents(p0: Marker?): View? = null
        })
        enableLocationLayer()
    }

    private fun onLocateButtonClicked() {
        startLocation()
    }

    private fun hasLocationPermission(): Boolean =
        ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED

    private fun requestLocationPermission() {
        requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION)
    }

    private fun enableLocationLayer() {
        val map = aMap ?: return
        if (hasLocationPermission()) {
            map.isMyLocationEnabled = true
        }
    }

    private fun startLocation() {
        if (!hasLocationPermission()) {
            pendingLocationRequest = true
            requestLocationPermission()
            return
        }
        enableLocationLayer()
        if (isLocating) return
        if (locationClient == null) {
            locationClient = AMapLocationClient(requireContext().applicationContext).apply {
                setLocationOption(
                    AMapLocationClientOption().apply {
                        isOnceLocation = true
                        isNeedAddress = false
                        locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
                    }
                )
                setLocationListener { location -> handleLocationResult(location) }
            }
        }
        isLocating = true
        binding.fabLocation.isEnabled = false
        binding.fabLocation.text = getString(R.string.map_action_locating)
        locationClient?.startLocation()
    }

    private fun handleLocationResult(location: AMapLocation?) {
        locationClient?.stopLocation()
        isLocating = false
        binding.fabLocation.isEnabled = true
        binding.fabLocation.text = getString(R.string.map_action_locate)
        if (location != null && location.errorCode == AMapLocation.LOCATION_SUCCESS) {
            lastKnownLocation = LatLng(location.latitude, location.longitude)
            if (lastPoints.isNotEmpty()) {
                renderPoints(lastPoints)
            }
            lastKnownLocation?.let { locationLatLng ->
                aMap?.animateCamera(
                    CameraUpdateFactory.newLatLngZoom(
                        locationLatLng,
                        14f
                    )
                )
            }
        } else {
            val errorInfo = location?.errorInfo ?: getString(R.string.map_location_failed)
            // 过滤掉高德地图KEY相关的错误提示
            val shouldShowError = !errorInfo.contains("KEY", ignoreCase = true) && 
                                 !errorInfo.contains("amap.com", ignoreCase = true) &&
                                 !errorInfo.contains("location-sdk", ignoreCase = true)
            
            if (shouldShowError && errorInfo != lastErrorMessage) {
                Snackbar.make(binding.root, errorInfo, Snackbar.LENGTH_LONG).show()
                lastErrorMessage = errorInfo
            }
            lastPoints.firstOrNull()?.let { fallback ->
                aMap?.animateCamera(
                    CameraUpdateFactory.newLatLngZoom(
                        LatLng(fallback.latitude, fallback.longitude),
                        11f
                    )
                )
            }
        }
    }

    private fun observeViewModel() {
        viewModel.uiState.observe(viewLifecycleOwner) { state ->
            renderState(state)
        }
    }

    private fun renderState(state: MapUiState) {
        binding.mapLoading.isVisible = state.isLoading
        binding.fabRefresh.isEnabled = !state.isLoading
        val hasError = state.error != null
        val displayPoints = filterPoints(state.points)
        val showEmpty = !state.isLoading && !hasError && displayPoints.isEmpty()

        binding.errorStateGroup.isVisible = hasError
        binding.emptyStateGroup.isVisible = showEmpty

        if (hasError) {
            binding.errorStateMessage.text = state.error
            if (state.error != lastErrorMessage) {
                Snackbar.make(binding.root, state.error ?: getString(R.string.map_error_state), Snackbar.LENGTH_LONG).show()
            }
            lastErrorMessage = state.error
        } else {
            lastErrorMessage = null
        }

        if (state.points != lastPoints) {
            lastPoints = state.points
            renderPoints(state.points)
        }
    }



    private fun renderPoints(points: List<MapPoint>) {
        val map = aMap ?: return
        val pointsToRender = filterPoints(points)
        markers.forEach { it.remove() }
        markers.clear()
        if (pointsToRender.size > CLUSTER_THRESHOLD) {
            renderClusteredPoints(map, pointsToRender)
        } else {
            pointsToRender.forEach { point -> addMarkerForPoint(map, point) }
        }
        if (lastKnownLocation == null && pointsToRender.isNotEmpty()) {
            map.moveCamera(
                CameraUpdateFactory.newLatLngZoom(
                    LatLng(pointsToRender[0].latitude, pointsToRender[0].longitude),
                    11f
                )
            )
        }
    }

    private fun filterPoints(points: List<MapPoint>): List<MapPoint> {
        val location = lastKnownLocation ?: return points
        return points.filter { point ->
            val results = FloatArray(1)
            Location.distanceBetween(
                location.latitude,
                location.longitude,
                point.latitude,
                point.longitude,
                results
            )
            results[0] <= NEARBY_RADIUS_METERS
        }
    }

    private fun renderClusteredPoints(map: AMap, points: List<MapPoint>) {
        val clusters = points.groupBy { point ->
            val latKey = (point.latitude / CLUSTER_GRID).toInt()
            val lngKey = (point.longitude / CLUSTER_GRID).toInt()
            latKey to lngKey
        }
        clusters.forEach { (_, clusterPoints) ->
            val avgLat = clusterPoints.map { it.latitude }.average()
            val avgLng = clusterPoints.map { it.longitude }.average()
            val totalLocks = clusterPoints.sumOf { it.lockCount }
            val openLocks = clusterPoints.sumOf { it.openCount }
            val alertLocks = clusterPoints.sumOf { it.malfunctionCount }
        val hasProblematic = clusterPoints.any { it.isProblematic() }
        val hasIdle = clusterPoints.any { it.isIdle() }
        val iconRes = when {
            hasProblematic -> R.drawable.ic_marker_alert
            hasIdle -> R.drawable.ic_marker_idle
            else -> R.drawable.ic_marker_safe
        }
            addClusterMarker(
                map,
                LatLng(avgLat, avgLng),
                getString(R.string.map_cluster_title, clusterPoints.size),
                getString(R.string.map_marker_snippet, totalLocks, openLocks, alertLocks),
                iconRes
            )
        }
    }

    private fun addMarkerForPoint(map: AMap, point: MapPoint) {
        val marker = map.addMarker(
            MarkerOptions()
                .position(LatLng(point.latitude, point.longitude))
                .title(point.roomName)
                .snippet(
                    getString(
                        R.string.map_marker_snippet,
                        point.lockCount,
                        point.openCount,
                        point.malfunctionCount
                    )
                )
                .icon(getMarkerDescriptor(markerIconForPoint(point)))
        )
        marker?.`object` = point
        marker?.let { markers += it }
    }

    private fun addClusterMarker(
        map: AMap,
        position: LatLng,
        title: String,
        snippet: String,
        @DrawableRes iconRes: Int
    ) {
        val marker = map.addMarker(
            MarkerOptions()
                .position(position)
                .title(title)
                .snippet(snippet)
                .icon(getMarkerDescriptor(iconRes))
        )
        marker?.let { markers += it }
    }

    @DrawableRes
    private fun markerIconForPoint(point: MapPoint): Int = when {
        point.isProblematic() -> R.drawable.ic_marker_alert
        point.isIdle() -> R.drawable.ic_marker_idle
        else -> R.drawable.ic_marker_safe
    }



    private fun getMarkerDescriptor(@DrawableRes resId: Int): BitmapDescriptor =
        markerDescriptorCache.getOrPut(resId) {
            val drawable = ContextCompat.getDrawable(requireContext(), resId) ?: return@getOrPut BitmapDescriptorFactory.defaultMarker()
            BitmapDescriptorFactory.fromBitmap(drawable.toBitmap())
        }

    private fun Drawable.toBitmap(): Bitmap = when (this) {
        is BitmapDrawable -> bitmap
        is VectorDrawable -> {
            val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(bitmap)
            setBounds(0, 0, canvas.width, canvas.height)
            draw(canvas)
            bitmap
        }
        else -> {
            val width = if (intrinsicWidth > 0) intrinsicWidth else 48
            val height = if (intrinsicHeight > 0) intrinsicHeight else 48
            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(bitmap)
            setBounds(0, 0, canvas.width, canvas.height)
            draw(canvas)
            bitmap
        }
    }

    private fun createInfoWindow(marker: Marker): View {
        val infoBinding = LayoutMapInfoWindowBinding.inflate(layoutInflater)
        val point = marker.`object` as? MapPoint
        infoBinding.tvRoomName.text = point?.roomName ?: marker.title.orEmpty()
        if (point != null) {
            val address = point.address?.takeIf { it.isNotBlank() } ?: getString(R.string.map_info_no_address)
            infoBinding.tvRegionPath.text = address
            infoBinding.tvRegionPath.isVisible = true
            infoBinding.tvLockSummary.text = getString(
                R.string.map_info_lock_summary,
                point.lockCount,
                point.openCount,
                point.malfunctionCount
            )
            infoBinding.btnViewDetail.isVisible = true
            infoBinding.btnViewDetail.setOnClickListener {
                navigateToRoomDetail(point.roomId)
                marker.hideInfoWindow()
            }
        } else {
            val snippet = marker.snippet.orEmpty()
            infoBinding.tvRegionPath.text = snippet.ifBlank { getString(R.string.map_info_no_address) }
            infoBinding.tvRegionPath.isVisible = snippet.isNotBlank()
            infoBinding.tvLockSummary.text = snippet
            infoBinding.btnViewDetail.isVisible = false
        }
        return infoBinding.root
    }

    private fun navigateToRoomDetail(roomId: Long) {
        val bundle = Bundle().apply { putLong("roomId", roomId) }
        findNavController().navigate(R.id.roomDetailFragment, bundle)
    }

    private fun showFilterDialog() {
        val state = viewModel.uiState.value ?: return
        val dialogBinding = DialogMapFilterBinding.inflate(layoutInflater)
        val statusOptions = buildStatusOptions()
        val statusAdapter = ArrayAdapter(
            requireContext(),
            android.R.layout.simple_dropdown_item_1line,
            statusOptions.map { it.label }
        )
        dialogBinding.statusDropdown.setAdapter(statusAdapter)

        val currentFilter = state.filter
        dialogBinding.statusDropdown.setText(
            statusOptions.firstOrNull { it.status == currentFilter.status }?.label
                ?: statusOptions.first().label,
            false
        )

        val dialog = MaterialAlertDialogBuilder(requireContext())
            .setView(dialogBinding.root)
            .create()

        dialogBinding.applyButton.setOnClickListener {
            val selectedStatus = statusOptions.getOrNull(statusOptions.indexOfFirst { it.label == dialogBinding.statusDropdown.text.toString() })
                ?: statusOptions.first()
            viewModel.applyFilter(MapFilter(regionId = null, status = selectedStatus.status))
            dialog.dismiss()
        }

        dialogBinding.resetButton.setOnClickListener {
            dialogBinding.statusDropdown.setText(statusOptions.first().label, false)
        }

        dialog.show()
    }

    private fun buildStatusOptions(): List<StatusOption> = listOf(
        StatusOption(getString(R.string.common_all), null),
        StatusOption(getString(R.string.room_status_safe), RoomStatus.NORMAL),
        StatusOption(getString(R.string.room_status_in_use), RoomStatus.IN_USE),
        StatusOption(getString(R.string.room_status_alert), RoomStatus.ALERT)
    )



    override fun onResume() {
        super.onResume()
        binding.mapView.onResume()
        startAutoRefresh()
    }

    override fun onPause() {
        super.onPause()
        binding.mapView.onPause()
        stopAutoRefresh()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        binding.mapView.onDestroy()
        locationClient?.stopLocation()
        locationClient?.onDestroy()
        locationClient = null
        markerDescriptorCache.clear()
        stopAutoRefresh()
        _binding = null
    }

    override fun onLowMemory() {
        super.onLowMemory()
        binding.mapView.onLowMemory()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_LOCATION) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                enableLocationLayer()
                if (pendingLocationRequest) {
                    pendingLocationRequest = false
                    startLocation()
                }
            } else {
                pendingLocationRequest = false
                Snackbar.make(binding.root, R.string.map_location_permission_denied, Snackbar.LENGTH_LONG).show()
            }
        }
    }

    /**
     * 启动自动刷新
     */
    private fun startAutoRefresh() {
        stopAutoRefresh() // 先停止之前的定时器
        
        refreshRunnable = object : Runnable {
            override fun run() {
                // 刷新地图数据
                viewModel.loadPoints()
                // 安排下次刷新
                refreshHandler.postDelayed(this, refreshIntervalMs)
            }
        }
        
        // 启动定时器
        refreshRunnable?.let { runnable ->
            refreshHandler.postDelayed(runnable, refreshIntervalMs)
        }
    }
    
    /**
     * 停止自动刷新
     */
    private fun stopAutoRefresh() {
        refreshRunnable?.let { runnable ->
            refreshHandler.removeCallbacks(runnable)
        }
        refreshRunnable = null
    }

    private data class StatusOption(val label: String, val status: RoomStatus?)

    companion object {
        private const val REQUEST_LOCATION = 101
        private const val CLUSTER_THRESHOLD = 80
        private const val CLUSTER_GRID = 0.02
        private const val NEARBY_RADIUS_METERS = 5000f
    }
}
